#coding:utf-8
#version: 1.0-20240618
import os, os.path, pathlib, paramiko, stat
from configparser import ConfigParser

class SyncSFTP():
    
    def connect_by_ini(self, ini_section):
        conf = ConfigParser()
        conf.read(pathlib.Path.home() / 'ftp.ini')
        # print(conf.sections(), conf[ini_section]['username'])
        self.connect(conf[ini_section]['host'], conf[ini_section]['port'],
            conf[ini_section]['username'], conf[ini_section]['pwd_or_key'])
    def connect(self, host, port, username, pwd_or_key):
        self.__sshclient = paramiko.SSHClient()
        # 自动接受所有主机密钥。
        self.__sshclient.set_missing_host_key_policy(paramiko.AutoAddPolicy())

        if '\\' in pwd_or_key or '/' in pwd_or_key:
            private_key = paramiko.RSAKey.from_private_key_file(pwd_or_key)
            self.__sshclient.connect(host, port=port, username=username, pkey=private_key)
        else:
            self.__sshclient.connect(host, port=port, username=username, password=pwd_or_key)
            
        self.__sftp = self.__sshclient.open_sftp()

    def __get_sftp_dirs(self, curr_ftp_path):
        # 获取当前目录下的所有条目
        directory_contents = self.__sftp.listdir_attr(curr_ftp_path)
        dirs,files_attr = [], []
        # 遍历条目，筛选出文件
        for item in directory_contents:
            if stat.S_ISDIR(item.st_mode):  # S_ISDIR是os模块中的常量，用于判断是否为目录
                dirs.append(item.filename)
            else:
                files_attr.append(item)
        return dirs, files_attr
        
    def __list_local_files(self, dirpath):
        dirs = []
        files = []
        for filename in os.listdir(dirpath):
            path = dirpath + '/' + filename
            if os.path.isfile(path):
                files.append(filename)
            else:
                dirs.append(filename)
        return dirs, files


    def __sync_sftp_files(self, curr_ftp_path):
        # 远程目录不存在
        try:
            self.__sftp.chdir(self.__ftp_root_path)
        except:
            self.__ftp_root_path = input("请输入需要同步的目录:")
            self.__sync_sftp_files(self.__ftp_root_path)
            return

        r_dirs, r_file_tuples = self.__get_sftp_dirs(curr_ftp_path)
        r_files = [x.filename for x in r_file_tuples]
        # print(r_dirs, r_files)

        curr_local_dir = curr_ftp_path.replace(self.__ftp_root_path, self.__local_root_path)
        l_dirs, l_files = self.__list_local_files(curr_local_dir)
        # print(l_dirs, l_files)

        print('\n----------------' + curr_ftp_path + '----------------')
        # 上传 本地新增的文件
        local_add_files = list(set(l_files).difference(set(r_files)))
        for relative_file_path in local_add_files:
            self.__uploda_file(relative_file_path, curr_ftp_path, curr_local_dir)

        # 下载 ftp多出的文件
        ftp_add_files = list(set(r_files).difference(set(l_files)))
        for relative_file_path in ftp_add_files:
            remote_file = curr_ftp_path+'/' + relative_file_path
            print ("下载FTP文件:"+remote_file)
            # self.__sftp.remove(remote_file)
            self.__download_file(relative_file_path, curr_ftp_path, curr_local_dir)

        # 上传 本地最近修改的文件
        cmp_files = list(set(l_files).intersection(set(r_files)))
        for relative_file_path in cmp_files:
            local_is_newer = self.__local_is_newer(relative_file_path, r_file_tuples, curr_ftp_path, curr_local_dir)
            if local_is_newer=="YES":
                self.__uploda_file(relative_file_path, curr_ftp_path, curr_local_dir)
            elif local_is_newer=="NO":
                self.__download_file(relative_file_path, curr_ftp_path, curr_local_dir)

        # 上传 本地多余的目录
        local_add_dirs = list(set(l_dirs).difference(set(r_dirs)))
        for d in local_add_dirs:
            ftp_add_dir = curr_ftp_path+'/'+ d
            print('创建FTP目录：' + ftp_add_dir)
            # os.mkdir(dp)
            self.__sftp.chdir(curr_ftp_path)
            self.__sftp.mkdir(ftp_add_dir)
            # 上传 本地多余的文件
            self.__sync_sftp_files(ftp_add_dir)

        # 下载 ftp多出的目录
        ftp_add_dirs = list(set(r_dirs).difference(set(l_dirs)))
        for d in ftp_add_dirs:
            ftp_add_dir = curr_ftp_path+'/'+ d
            # self.__del_sftp_dir(ftp_add_dir)
            # 上传 本地多余的文件
            local_new_dir = os.path.join(curr_local_dir, d)
            print('\n下载FTP目录：' + ftp_add_dir)
            os.makedirs(local_new_dir, exist_ok=True)
            self.__sync_sftp_files(ftp_add_dir)

        # 检查已经存在ftp是否和本地相同
        inter_dirs = set(r_dirs)&set(l_dirs)
        for dn in inter_dirs:
            self.__sync_sftp_files(curr_ftp_path + '/' + dn)

    def __download_file(self, file_path, curr_ftp_path, curr_local_dir):
        remote_file = curr_ftp_path+'/' + file_path
        self.__sftp.get(remote_file, curr_local_dir + '/' + file_path)
        print('>>>下载：' + remote_file)

    def __uploda_file(self, relative_file_path, curr_ftp_path, curr_local_dir):
        local_file, remote_file = curr_local_dir + '/' + relative_file_path,  curr_ftp_path + '/' + relative_file_path
        try:
            print("\t上传", local_file)
            self.__sftp.put(local_file, remote_file)
        except Exception as err:
            print(err)

    def __local_is_newer(self, file_path, r_file_tuples, curr_ftp_path, curr_local_path):
        r_file_attr = [x for x in r_file_tuples if x.filename==file_path][0]
        size_r = r_file_attr.st_size
        # tz_offset = -1*time.timezone if (time.localtime().tm_isdst == 0) else time.altzone
        mtime_r = r_file_attr.st_mtime # + tz_offset

        local_file = curr_local_path + '/' + file_path
        size_l = os.path.getsize(local_file)

        if size_r!=size_l:
            mtime_l = int(os.path.getmtime(local_file))
            # print(mtime_r, mtime_l)
            if mtime_l > mtime_r:
                print("本地新：", file_path, size_l, mtime_l, ">", size_r, mtime_r)
                return "YES"
            else:
                print("远程新：", file_path, size_l, mtime_l, "<", size_r, mtime_r)
                return "NO"
        else:
            return "NO-OP"

    def __del_sftp_dir(self, curr_ftp_path):
        print("开始进入目录："+curr_ftp_path)
        r_dirs, r_file_tuples = self.__get_sftp_dirs(curr_ftp_path)
        # r_file_tuples = self.__get_ftp_files(curr_ftp_path)
        r_files = [x.filename for x in r_file_tuples]

        self.__sftp.chdir(curr_ftp_path)
        for fn in r_files:
            print("\t删除FTP文件："+curr_ftp_path+'/'+fn)
            self.__sftp.remove(fn)

        for dir in r_dirs:
            ftp_path = curr_ftp_path+'/'+dir
            self.__del_sftp_dir(ftp_path)

        print ("\t删除目录："+curr_ftp_path)
        self.__sftp.chdir('..')
        self.__sftp.rmdir(curr_ftp_path)
    def sync(self, ftp_root_path, local_root_path=os.getcwd()):
        self.__ftp_root_path = ftp_root_path
        self.__local_root_path = local_root_path 
        self.__sync_sftp_files(self.__ftp_root_path)
        self.__sftp.close()
        self.__sshclient.close()

if __name__ == '__main__':
    FTP_ROOT_PATH = '/t2'

    sf = SyncSFTP()
    sf.connect_by_ini("ftp7")
    sf.sync(FTP_ROOT_PATH, "e:/t1")
    os.system('pause')